//#condition MUJMAIL_HTML
package mujmail.html;

import java.util.Hashtable;

/**
 * Class representing HTML token.
 * 
 * @author Jan Gregor (html, head, body, p, b)
 * @author Betlista
 */
public class Token {

    private static final int TYPE_BASE = 100;

    public static final int TYPE_END                     = TYPE_BASE + 1; // end of file
    public static final int TYPE_TEXT                    = TYPE_BASE + 2; //
    public static final int TYPE_ELEMENT_FIRST_TAG_START = TYPE_BASE + 3; // <
    public static final int TYPE_ELEMENT_FIRST_TAG_END   = TYPE_BASE + 4; // > or />
    public static final int TYPE_ELEMENT_FINISHING_TAG   = TYPE_BASE + 5;
    public static final int TYPE_ARGUMENT_NAME           = TYPE_BASE + 6; // 
    public static final int TYPE_ARGUMENT_VALUE          = TYPE_BASE + 7; // 
    public static final int TYPE_ENTITY                  = TYPE_BASE + 8; // &nbsp; &amp; ...
    public static final int TYPE_UNPAIRED                = TYPE_BASE + 9;

    // 4 = supported elements count
    // table is two times bigger to less probability of collision
    protected static final Hashtable elementCodes = new Hashtable(2 * 4); 

    private static final int ELEMENT_BASE = 200;

    public static final int ELEMENT_HTML  = ELEMENT_BASE + 1; 
    public static final int ELEMENT_HEAD  = ELEMENT_BASE + 2; 
    public static final int ELEMENT_BODY  = ELEMENT_BASE + 3; 
    public static final int ELEMENT_P     = ELEMENT_BASE + 4; 
    public static final int ELEMENT_B     = ELEMENT_BASE + 5; 
    public static final int ELEMENT_BR    = ELEMENT_BASE + 6; 
    public static final int ELEMENT_HR    = ELEMENT_BASE + 7; 
    
    
    public static final int IGNORED_ELEMENT = -1;
    
    static {
        elementCodes.put("html", new Integer(ELEMENT_HTML));
        elementCodes.put("head", new Integer(ELEMENT_HEAD));
        elementCodes.put("body", new Integer(ELEMENT_BODY));
        elementCodes.put("p"   , new Integer(ELEMENT_P));
        elementCodes.put("b"   , new Integer(ELEMENT_B));
        elementCodes.put("br"  , new Integer(ELEMENT_BR));
        elementCodes.put("hr"  , new Integer(ELEMENT_HR));
    }
    
    //TODO: more elements

    /**
     * Returns code for element name.
     * 
     * @returns code for element name
     */
    public static int getElementCode(String elementName) {
        Integer code = (Integer) elementCodes.get(elementName);
        if (code != null) {
            return code.intValue();
        } else {
            return IGNORED_ELEMENT;
        }
    }

    public static String getTypeName(final int type) {
    	switch ( type ) {
	    	case TYPE_ARGUMENT_NAME:
	    		return "TYPE_ARGUMENT_NAME";
	    	case TYPE_ARGUMENT_VALUE:
	    		return "TYPE_ARGUMENT_VALUE";
	    	case TYPE_BASE:
	    		return "UNSET";
	    	case TYPE_ELEMENT_FINISHING_TAG:
	    		return "TYPE_ELEMENT_FINISHING_TAG";
	    	case TYPE_ELEMENT_FIRST_TAG_END:
	    		return "TYPE_ELEMENT_FIRST_TAG_END";
	    	case TYPE_ELEMENT_FIRST_TAG_START:
	    		return "TYPE_ELEMENT_FIRST_TAG_START";
	    	case TYPE_END:
	    		return "TYPE_END";
	    	case TYPE_ENTITY:
	    		return "TYPE_ENTITY";
	    	case TYPE_TEXT:
	    		return "TYPE_TEXT";
            case TYPE_UNPAIRED:
                return "TYPE_UNPAIRED";
	    	default:
	    		return "UNKNOWN";
    	}
    }

    public static String getElementName( final int elementCode ) {
    	switch ( elementCode ) {
	    	case ELEMENT_BASE:
	    		return "UNSET";
	    	case ELEMENT_HTML:
	    		return "HTML";
	    	case ELEMENT_HEAD:
	    		return "HEAD";
	    	case ELEMENT_BODY:
	    		return "BODY";
	    	case ELEMENT_P:
	    		return "P";
	    	case ELEMENT_B:
	    		return "B";
            case ELEMENT_BR:
                return "BR";
            case ELEMENT_HR:
                return "HR";
    		default:
    			return "UNKNOWN";
    	}
    }

    /**
     * Token type.</br>
     * Available values:
     * <ul>
     * <li>{@link #TYPE_END}</li>
     * <li>{@link #TYPE_TEXT}</li>
     * <li>{@link #TYPE_ELEMENT_FIRST_TAG_START}</li>
     * <li>{@link #TYPE_ELEMENT_FIRST_TAG_END}</li>
     * <li>{@link #TYPE_ELEMENT_FINISHING_TAG}</li>
     * <li>{@link #TYPE_ARGUMENT_NAME}</li>
     * <li>{@link #TYPE_ARGUMENT_VALUE}</li>
     * <li>{@link #TYPE_ENTITY}</li>
     * </ul>
     */
    private int    type;
    private int    elementCode = IGNORED_ELEMENT;
    private Object value       = null;

    /**
     * Creates Token instance with just type defined.</br>
     * Example of such Token is {@link #TYPE_END}.
     * 
     *  @param type type of the token
     */
    public Token(int type) {
        this(type, IGNORED_ELEMENT, null);
    }

    /**
     * Creates Token instance with defined type and value.
     * Such tokens are e.g. {@link #TYPE_ARGUMENT_NAME}
     * and {@link #TYPE_ARGUMENT_VALUE}
     *  
     * @param type token type, see {@link #type}
     * @param value token value, e.g. argument name/value
     */
    public Token(int type, Object value) {
        this(type, IGNORED_ELEMENT, value);
    }

    /**
     * Creates Token instance with defined type, element and value.
     *  
     * @param type token type, see {@link #type}
     * @param elementCode code for element
     * @param value token value, e.g. argument name/value
     */
    public Token(int type, int elementCode, Object value) {
        this.type        = type;
        this.elementCode = elementCode;
        this.value       = value;
    }

    /* ***************
     *    getters    *
     *****************/

    /**
     * Return token type.
     * @returns token type
     */
    public int getType() {
        return this.type;
    }

    /**
     * Returns element code
     * @returns element code
     */
    public int getElementCode() {
        return this.elementCode;
    }

    /**
     * Returns token value.
     * @return token value
     */
    public Object getValue() {
        return this.value;
    }

    /* ***************
     *    setters    *
     *****************/

    /**
     * Sets type for element.</br>
     * Reason for needfulness of this method is in processing unpaired tags.
     * When there is unpaired tag, the next token is
     * {@link #TYPE_ELEMENT_FIRST_TAG_END}, in fact it have to be
     * {@link #TYPE_ELEMENT_FINISHING_TAG}, but when we use this one the section
     * is closed, so new type was introduced - {@link #TYPE_UNPAIRED} which
     * doesn't break the section processing.
     * 
     * @param type new token type
     */
    public void setType(int type) {
        this.type = type;
    }

    /* **********************
     *    Object methods    *
     ************************/

    //#ifdef MUJMAIL_DEVELOPMENT
    /**
     * Returns string representation for this class in format:<br>
     * Token[type='value' (typeInt), elementCode='value' (elemtnCodeInt), value=value]<br>
     * 
     * @return string representation of this class
     */
    public String toString() {
    	StringBuffer buff = new StringBuffer();
    	
    	buff.append("Token[")
    		.append("type='").append( getTypeName( type ) ).append("' (")
    			.append( type ).append(")")
    		.append(", elementCode='").append( getElementName( elementCode) )
    			.append("' (").append( elementCode ).append(")")
    		.append(", value=").append( String.valueOf(value) )
    		.append(']')
    		;

    	return buff.toString();
    }
    //#endif

    /**
     * hashCode generated by Eclipse.
     */
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + elementCode;
		result = prime * result + type;
		result = prime * result + ((value == null) ? 0 : value.hashCode());
		return result;
	}

    /**
     * equals method generated by Eclipse.
     */
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Token other = (Token) obj;
		if (elementCode != other.elementCode)
			return false;
		if (type != other.type)
			return false;
		if (value == null) {
			if (other.value != null)
				return false;
		} else if (!value.equals(other.value))
			return false;
		return true;
	}
}
